package com.pool.socket;

import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

import org.json.JSONException;
import org.json.JSONObject;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.pool.socket.DataType.*;

public class DataHandler extends Thread {

	long useNumber = 0;

	byte[] buffer = null;
	Socket conn = null;
	int localGatewayId = 0;
	private static long handlerId = 0;

	DataHandler() {
		this.setName("DataHandler" + handlerId++);
		System.out.println(this.getName());
	}

	public void fillProperty(int gatewayId, byte[] data, Socket socket) {
		this.localGatewayId = gatewayId;
		this.buffer = data;
		this.conn = socket;
	}

	public void resetData() {
		this.localGatewayId = 0;
		this.buffer = null;
		this.conn = null;
	}

	private void consumeData(byte[] data) {
		int packetHeaderLen = MessageFormat.packetHeaderLen;
		ByteBuffer bb = ByteBuffer.wrap(data);
		bb.order(MessageFormat.byteOrder);
		byte[] header = new byte[packetHeaderLen];
		bb.get(header);
		int len = MessageFormat.byteArrayToPacketLen(header);
		do {
			if (bb.remaining() >= len) {
				byte[] packet = new byte[packetHeaderLen + len];
				System.arraycopy(header, 0, packet, 0, packetHeaderLen);
				bb.get(packet, packetHeaderLen, len);
				processPacket(packet);
			}
			if (bb.remaining() >= packetHeaderLen) {
				bb.get(header);
				len = MessageFormat.byteArrayToPacketLen(header);
			} else
				break;
		} while (len > 0);
	}

	private UUID modifyVirtualGatewayFile(int localGatewayId, int sensorId) {
		UUID uuid = null;
		VirtualGateway gateway = null;
		synchronized (SocketServer.GlobalToLocalId) {
			Gson gson = new Gson();
			JsonObject jsonObj = new JsonObject();
			jsonObj.addProperty("GatewayId", localGatewayId);
			jsonObj.addProperty("SensorId", sensorId);
			String jsonKey = gson.toJson(jsonObj);
			if (SocketServer.LocalIdToGlobal.get(jsonKey) == null) {
				System.out.println("Gateway file should be modified!");
				uuid = UUID.randomUUID();
				SocketServer.LocalIdToGlobal.put(jsonKey, uuid);
				SocketServer.GlobalToLocalId.put(uuid, jsonKey);
				gateway = SocketServer.getGatewayById(localGatewayId);
				Sensor sensor = new Sensor();
				sensor.setSensorId(sensorId);
				sensor.setSensorUUID(uuid);
				List<Sensor> sensors = gateway.getSensors();
				sensors.add(sensor);
				gateway.setSensors(sensors);

			} else {
				uuid = SocketServer.LocalIdToGlobal.get(jsonKey);
			}
		}
		if (gateway != null) {
			File file = new File(gateway.getFileName());
			JAXBContext jaxbContext;
			try {
				jaxbContext = JAXBContext.newInstance(VirtualGateway.class);
				Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
				jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
						true);
				jaxbMarshaller.marshal(gateway, file);
			} catch (JAXBException e) {
				e.printStackTrace();
			}
		}
		return uuid;
	}

	private void processReceiveData(byte[] packet) {
		ByteBuffer bb = ByteBuffer.wrap(packet);
		bb.order(MessageFormat.byteOrder);
		bb.position(10);
		int gatewayId = bb.getInt();
		short len = bb.getShort();
		if (len > 0) {
			byte[] buf = new byte[len];
			bb.get(buf);
			try {
				String json = new String(buf, "UTF-8");
				try {
					JSONObject obj = new JSONObject(json);
					int sensorId = obj.getInt("SensorId");
					String DcTime = obj.getString("DcTime");
					String value = obj.getString("Value");
					UUID uuid = modifyVirtualGatewayFile(gatewayId, sensorId);
					StringBuilder builder = new StringBuilder();
					builder.append("SensorId = " + uuid.toString() + ",");
					builder.append("DcTime = " + DcTime + ",");
					builder.append("Value = " + value);
					// System.out.println(builder.toString());
				} catch (JSONException e) {
					e.printStackTrace();
				}
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		}
	}

	private void processPacket(byte[] packet) {
		if (MessageFormat.isHeartBeatMessage(packet)) {
			System.out.println("receive heartbeat message!");
			processHeartBeat(conn, packet);
		} else if (MessageFormat.isSensorData(packet)) {
			processReceiveData(packet);
		} else if (MessageFormat.isRequestConfirm(packet)) {
			System.out
					.println("TCP : Reveive Node Config Request Confirm - addr");
		} else if (MessageFormat.isAddressList(packet)) {
			System.out.println("TCP : Reveive address list!");
		} else if (MessageFormat.isIntervalSetConfirm(packet)) {
			System.out
					.println("TCP : Reveive Interval set Confirm from socket");
		} else {
			System.out.println("Should not come here!");
		}
	}

	public void run() {
		try {
			while (true) {
				if ((buffer != null) && (conn != null) && (localGatewayId > 0)) {
					validateGatewayId(localGatewayId);
					consumeData(buffer);
					// System.out.println(this.getName() + ", use number = "
					// + this.useNumber++);
				}
				Queue<DataHandler> handlers = SocketServer.getHandlers();
				synchronized (handlers) {
					// handlers.add(this);
					handlers.offer(this);
					resetData();
					// System.out.println(this.getName()
					// + ",Handler list's size = " + handlers.size());
				}
				synchronized (this) {
					this.wait(); // sleep
				}

			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	private boolean validateGatewayId(int localAddr) {
		if (localAddr <= 0)
			return false;
		JsonObject jsonObj = new JsonObject();
		jsonObj.addProperty("GatewayId", localAddr);
		jsonObj.addProperty("SensorId", 0);
		String jsonStr = new Gson().toJson(jsonObj);
		HashMap<Socket, SocketDepict> pool = SocketServer.getSocketPool();
		synchronized (SocketServer.GlobalToLocalId) {
			synchronized (pool) {
				UUID uuid = SocketServer.LocalIdToGlobal.get(jsonStr);
				if (uuid == null) {
					System.out.println("Gateway should hava uuid!");
					return false;
				}
				ArrayList<Socket> socketList = SocketServer.GlobalIdToSocket
						.get(uuid);
				if (socketList == null) {
					socketList = new ArrayList<Socket>();
					socketList.add(conn);
					SocketServer.GlobalIdToSocket.put(uuid, socketList);
					pool.get(conn).setGatewayUUID(uuid);
				} else {
					if (socketList.isEmpty()) {
						socketList.add(conn);
						SocketServer.GlobalIdToSocket.put(uuid, socketList);
						pool.get(conn).setGatewayUUID(uuid);
					} else {
						if (!socketList.contains(conn))
							return false;
					}
				}
			}
		}
		return true;
	}

	private void processHeartBeat(Socket socket, byte[] data) {
		ByteBuffer bb = ByteBuffer.wrap(data);
		bb.order(MessageFormat.byteOrder);
		byte[] tmp = new byte[17];
		bb.get(tmp, 0, 17);
		byte beat1 = bb.get();
		HashMap<Socket, SocketDepict> pool = SocketServer.getSocketPool();
		synchronized (pool) {
			SocketDepict description = pool.get(socket);
			byte beat2 = description.getHeartbeat();
			System.out.println("beat1, beat2 = " + beat1 + " , " + beat2);
			if (beat1 == ((beat2 + 1) % 128)) {
				description.number = 0;
				description.setHeartbeat(beat1);// not expect receive heart beat
			}
		}
	}
}
